package bblgw

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"runtime/debug"
	"strings"
	"time"
)

func NewGatewayMux(gw_conf GatewayConfig) *GatewayMux {
	mux := &GatewayMux{
		cache_dir:     gw_conf.Cachedir,
		cache_tmp_dir: gw_conf.Cachedir + "/.tmp",
		ms_master:     gw_conf.Master,
		ms_slave:      gw_conf.Slaves,
		config:        gw_conf,
		client:        NewStoreClient(gw_conf.Master, gw_conf.Slaves),
	}
	os.RemoveAll(mux.cache_tmp_dir)
	if err := os.MkdirAll(mux.cache_dir, os.ModePerm); err != nil {
		log.Error(err)
	}
	if err := os.MkdirAll(mux.cache_tmp_dir, os.ModePerm); err != nil {
		log.Error(err)
	}
	return mux
}

type GatewayMux struct {
	cache_dir     string
	cache_tmp_dir string
	ms_master     string
	ms_slave      []string
	config        GatewayConfig
	client        *StoreClient
}

func notFound(rw http.ResponseWriter, req *http.Request) {
	http.NotFound(rw, req)
}
func badRequest(rw http.ResponseWriter, req *http.Request) {
	http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
func internalServerError(rw http.ResponseWriter, req *http.Request) {
	http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
func forbidden(rw http.ResponseWriter, req *http.Request) {
	http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
}

type HandlerFunc func(mux *GatewayMux, rw http.ResponseWriter, req *http.Request)

var (
	op = map[string]HandlerFunc{
		"HEAD":     do_head,
		"GET":      do_get,
		"POST":     do_post,
		"PUT":      do_put,
		"DELETE":   do_delete,
		"TRUNCATE": do_truncate,
		"PURGE":    do_purge,
		// "OPEN":     do_open,
		// "APPEND":   do_append,
		// "CLOSE":    do_close,
		"COPY":   do_copy,
		"MOVE":   do_move,
		"LINK":   do_link,
		"UNLINK": do_unlink,
	}
)

func (m *GatewayMux) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	log.Debug(req.RemoteAddr, req.Header.Get("X-Forward-For"), req.Header.Get("Host"), req.Method, req.URL.String())
	defer func() {
		if e := recover(); e != nil {
			log.Error(string(debug.Stack()))
			internalServerError(rw, req)
		}
	}()
	rw.Header().Set("Server", server_name())
	if handler, exist := op[req.Method]; exist {
		handler(m, rw, req)
	} else {
		badRequest(rw, req)
	}
}

func do_head(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	stat, err := mux.client.Stat(req.URL.Path)
	if err != nil {
		log.Error(err)
		notFound(rw, req)
		return
	}
	rw.Header().Set("Content-Length", fmt.Sprintf("%v", stat.Size))
	rw.Header().Set("Last-Modified", fmt.Sprintf("%v", time.Unix(stat.MTime, 0)))
}

func file_exist(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil || os.IsExist(err)
}

func do_purge(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	host := req.Header.Get("X-Host")
	if host == "" {
		host = req.Host
	}
	cache_path := filepath.Join(mux.cache_dir, host, req.URL.Path)

	if file_exist(cache_path) {
		if err := os.RemoveAll(cache_path); err != nil {
			forbidden(rw, req)
			return
		}
	}
}

func do_get(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	if req.URL.Path == "/" || "" == req.URL.Path {
		badRequest(rw, req)
		return
	}
	cache_path := filepath.Join(mux.cache_dir, req.Host, req.URL.Path)
	if file_exist(cache_path) {
		http.ServeFile(rw, req, cache_path)
	} else {
		downFunc := func() {
			tmpfilename := filepath.Join(mux.cache_tmp_dir, req.Host+strings.Replace(req.URL.Path, "/", "_", -1))
			if file_exist(tmpfilename) {
				return
			}
			tmpf, err := os.Create(tmpfilename)
			if err != nil {
				//log.Error(err)
				return
			}
			_, err = mux.client.ReadToWriter(req.URL.Path, tmpf)
			if err != nil {
				tmpf.Close()
				log.Error(err)
				return
			}
			if file_exist(cache_path) {
				os.Remove(tmpf.Name())
			} else {
				os.MkdirAll(filepath.Dir(cache_path), os.ModePerm)
				if err = os.Rename(tmpf.Name(), cache_path); err != nil {
					os.Remove(tmpf.Name())
					log.Error(err)
					return
				}
			}
		}
		go downFunc()
		mux.client.ReadProxy(rw, req)
	}

}
func is_multipart(r *http.Request) bool {
	ct := r.Header.Get("Content-Type")
	return strings.HasPrefix(ct, "multipart") || strings.HasPrefix(ct, "Multipart")
}
func do_put(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	cache_path := filepath.Join(mux.cache_dir, req.Host, req.URL.Path)
	if file_exist(cache_path) {
		os.Remove(cache_path)
	}
	if is_multipart(req) {
		file, _, err := req.FormFile("object")
		if err != nil {
			internalServerError(rw, req)
			log.Error(err)
			return
		}
		defer file.Close()
		written, err := mux.client.WriteFromReader(file, req.ContentLength, req.URL.Path)
		log.Debug("mux.client.WriteFromReader", written)
		if err != nil {
			log.Error(err)
			internalServerError(rw, req)
			return
		}
		rw.Header().Set("X-WRITTEN-SIZE", fmt.Sprintf("%v", written))
		log.Debug("written", written)
		rw.Write([]byte(fmt.Sprintf("{\"code\":0,\"size\":%v}", written)))
	} else {
		written, err := mux.client.WriteFromReader(req.Body, req.ContentLength, req.URL.Path)
		log.Debug("mux.client.WriteFromReader", written)
		if err != nil {
			log.Error(err)
			internalServerError(rw, req)
			return
		}
		rw.Header().Set("X-WRITTEN-SIZE", fmt.Sprintf("%v", written))
		log.Debug("written", written)
		rw.Write([]byte(fmt.Sprintf("{\"code\":0,\"size\":%v}", written)))
	}

}

func do_post(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	do_put(mux, rw, req)
}

func do_delete(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	cache_path := filepath.Join(mux.cache_dir, req.Host, req.URL.Path)
	if file_exist(cache_path) {
		os.Remove(cache_path)
	}
	err := mux.client.Delete(req.URL.Path)
	if err != nil {
		log.Error(err)
		notFound(rw, req)
		return
	}
}

func do_truncate(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	cache_path := filepath.Join(mux.cache_dir, req.Host, req.URL.Path)
	if file_exist(cache_path) {
		os.Remove(cache_path)
	}
	err := mux.client.Truncate(req.URL.Path)
	if err != nil {
		log.Error(err)
		notFound(rw, req)
		return
	}
}

// func do_open(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
// 	fd, err := mux.client.Open(req.URL.Path)
// 	if err != nil {
// 		log.Error(err)
// 		internalServerError(rw, req)
// 		return
// 	}
// 	rw.Header().Add("X-OBJECT-FD", fd)
// }

// func do_append(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
// 	err := mux.client.Append(req.Header.Get("X-OBJECT-FD"), req.Body, req.ContentLength)
// 	if err != nil {
// 		log.Error(err)
// 		notFound(rw, req)
// 		return
// 	}
// }

// func do_close(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {

// }

// func do_copy(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {

// }

func do_move(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	cache_path := filepath.Join(mux.cache_dir, req.Host, req.URL.Path)
	if file_exist(cache_path) {
		os.Remove(cache_path)
	}
	err := mux.client.Rename(req.URL.Path, req.Header.Get("X-RENAME-TO"))
	if err != nil {
		log.Error(err)
		notFound(rw, req)
		return
	}
}

func do_link(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	cache_path := filepath.Join(mux.cache_dir, req.Host, req.URL.Path)
	if file_exist(cache_path) {
		os.Remove(cache_path)
	}
	err := mux.client.Link(req.URL.Path, req.Header.Get("X-LINK-TO"))
	if err != nil {
		log.Error(err)
		notFound(rw, req)
		return
	}
}

func do_unlink(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {
	cache_path := filepath.Join(mux.cache_dir, req.Host, req.URL.Path)
	if file_exist(cache_path) {
		os.Remove(cache_path)
	}
	err := mux.client.Unlink(req.URL.Path)
	if err != nil {
		log.Error(err)
		notFound(rw, req)
		return
	}
}

func do_copy(mux *GatewayMux, rw http.ResponseWriter, req *http.Request) {

}
